/**
 * Copyright (c) 2006-2011 LOVE Development Team
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 **/

#include "Pool.h"

#include "Source.h"

namespace love
{
    namespace audio
    {
        namespace openal
        {
            Pool::Pool()
            {
                // Generate sources.
                alGenSources(NUM_SOURCES, sources);
                
                // Create the mutex.
                mutex = new thread::Mutex();
                
                if(alGetError() != AL_NO_ERROR)
                    throw love::Exception("Could not generate sources.");
                
                // Make all sources available initially.
                for(int i = 0; i < NUM_SOURCES; i++)
                    available.push(sources[i]);
            }
            
            Pool::~Pool()
            {
                stop();
                
                delete mutex;
                
                // Free all sources.
                alDeleteSources(NUM_SOURCES, sources);
            }
            
            bool Pool::isAvailable() const
            {
                bool has = false;
                {
                    thread::Lock lock(mutex);
                    has = !available.empty();
                }
                return has;
            }
            
            bool Pool::isPlaying(Source * s)
            {
                bool p = false;
                {
                    thread::Lock lock(mutex);
                    for(std::map<Source *, ALuint>::iterator i = playing.begin(); i != playing.end(); i++)
                    {
                        if(i->first == s)
                            p = true;
                    }
                }
                return p;
            }
            
            void Pool::update()
            {
                thread::Lock lock(mutex);
                
                std::map<Source *, ALuint>::iterator i = playing.begin();
                
                while(i != playing.end())
                {
                    if(!i->first->update())
                    {
                        i->first->stopAtomic();
                        i->first->rewindAtomic();
                        i->first->release();
                        available.push(i->second);
                        playing.erase(i++);
                    }
                    else
                    { 
                        ++i;
                    }
                }
            }
            
            int Pool::getNumSources() const
            {
                return playing.size();
            }
            
            int Pool::getMaxSources() const
            {
                return NUM_SOURCES;
            }
            
            bool Pool::play(Source * source, ALuint & out)
            {
                bool ok;
                out = 0;
                
                thread::Lock lock(mutex);
                
                bool alreadyPlaying = findSource(source, out);
                
                
                if(!alreadyPlaying)
                {
                    // Try to play.
                    if(!available.empty())
                    {
                        // Get the first available source.
                        out = available.front();
                        
                        // Remove it.
                        available.pop();
                        
                        // Insert into map of playing sources.
                        playing.insert(std::pair<Source *, ALuint>(source, out));
                        
                        source->retain();
                        
                        source->playAtomic();
                        
                        ok = true;
                    }
                    else
                    {
                        ok = false;
                    }
                }
                else
                {
                    ok = true;
                }
                
                return ok;
            }
            
            void Pool::stop()
            {
                thread::Lock lock(mutex);
                for(std::map<Source *, ALuint>::iterator i = playing.begin(); i != playing.end(); i++)
                {
                    i->first->stopAtomic();
                    i->first->release();
                    available.push(i->second);
                }
                
                playing.clear();
            }
            
            void Pool::stop(Source * source)
            {
                thread::Lock lock(mutex);
                removeSource(source);
            }
            
            void Pool::pause()
            {
                thread::Lock lock(mutex);
                for(std::map<Source *, ALuint>::iterator i = playing.begin(); i != playing.end(); i++)
                    i->first->pauseAtomic();
            }
            
            void Pool::pause(Source * source)
            {
                thread::Lock lock(mutex);
                ALuint out;
                if(findSource(source, out))
                    source->pauseAtomic();
            }
            
            void Pool::resume()
            {
                thread::Lock lock(mutex);
                for(std::map<Source *, ALuint>::iterator i = playing.begin(); i != playing.end(); i++)
                    i->first->resumeAtomic();
            }
            
            void Pool::resume(Source * source)
            {
                thread::Lock lock(mutex);
                ALuint out;
                if(findSource(source, out))
                    source->resumeAtomic();
            }
            
            void Pool::rewind()
            {
                thread::Lock lock(mutex);
                for(std::map<Source *, ALuint>::iterator i = playing.begin(); i != playing.end(); i++)
                    i->first->rewindAtomic();
            }
            
            // For those times we don't need it backed.
            void Pool::softRewind(Source * source)
            {
                thread::Lock lock(mutex);
                source->rewindAtomic();
            }
            
            void Pool::rewind(Source * source)
            {
                thread::Lock lock(mutex);
                source->rewindAtomic();
            }
            
            void Pool::release(Source * source)
            {
                ALuint s = findi(source);
                
                if(s != 0)
                {
                    available.push(s);
                    playing.erase(source);
                }
            }
            
            void Pool::seek(Source * source, float offset, void * unit)
            {
                thread::Lock lock(mutex);
                return source->seekAtomic(offset, unit);
            }
            
            float Pool::tell(Source * source, void * unit)
            {
                thread::Lock lock(mutex);
                return source->tellAtomic(unit);
            }
            
            ALuint Pool::findi(const Source * source) const
            {
                std::map<Source *, ALuint>::const_iterator i = playing.find((Source *)source);
                
                if(i != playing.end())
                    return i->second;
                
                return 0;
            }
            
            bool Pool::findSource(Source * source, ALuint & out)
            {
                std::map<Source *, ALuint>::const_iterator i = playing.find((Source *)source);
                
                bool found = i != playing.end();
                
                if(found)
                    out = i->second;
                
                return found;
            }
            
            bool Pool::removeSource(Source * source)
            {
                std::map<Source *, ALuint>::iterator i = playing.find((Source *)source);
                
                if(i != playing.end())
                {
                    source->stopAtomic();
                    available.push(i->second);
                    playing.erase(i++);
                    source->release();
                    return true;
                }
                
                return false;
            }
            
        } // openal
    } // audio
} // love
